package com.guang.springbootmybatisautogenerate.page.intercepters;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.guang.springbootmybatisautogenerate.page.bean.Page;
import com.guang.springbootmybatisautogenerate.page.bean.PagingRowBounds;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.ResultMap.Builder;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

@Intercepts({@Signature(
    type = Executor.class,
    method = "query",
    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
), @Signature(
    type = Executor.class,
    method = "query",
    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)})
public class PagingInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("--------------||||||||||----------------");
        RowBounds rowBounds = (RowBounds)invocation.getArgs()[2];
        Object[] args = invocation.getArgs();
        if (rowBounds != null && rowBounds instanceof PagingRowBounds) {
            System.out.println("是否使用我们的分页");
            // 获取得到分页对象
            PagingRowBounds pagingRowBounds = (PagingRowBounds)rowBounds;
            // 获取得到执行器对象
            Executor executor = (Executor)invocation.getTarget();
            // 获取得到封装的映射语句对象
            MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];
            // 获取得到参数
            Object parameterObject = invocation.getArgs()[1];
            // 调用executor传入进来的第三个参数ResultHandler
            ResultHandler resultHandler = (ResultHandler)invocation.getArgs()[3];
            // 如果传入的参数的个数大于四个，那么应该有对应的缓存等条件，那么获取得到BoundSql。BoundSql中封装的有
            BoundSql boundSql = invocation.getArgs().length > 4 ? (BoundSql)invocation.getArgs()[5] : mappedStatement.getBoundSql(parameterObject);
            // 校验操作
            this.validate(mappedStatement, boundSql);
            // 统计多少条数据
            Integer totalCount = this.executeCountSql(executor, mappedStatement, parameterObject, pagingRowBounds, resultHandler, boundSql);
            // 执行真正的结构数据
            List result = this.executePagingSql(executor, mappedStatement, parameterObject, pagingRowBounds, resultHandler, boundSql);
            return new Page(totalCount, pagingRowBounds.getCurrPage(), pagingRowBounds.getPageSize(), result);
        } else {
            return invocation.proceed();
        }
    }

    // 查询得到的最终数据
    private Integer executeCountSql(Executor executor, MappedStatement mappedStatement, Object parameterObject, PagingRowBounds pagingRowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        BoundSql countBoundSql = this.createCountSql(mappedStatement, boundSql);
        // 获取得到新的映射语句
        MappedStatement countMappedStatement = this.createCountMappedStatement(mappedStatement);
        // 创建缓存key，根据新的构建条件
        CacheKey cacheKey = executor.createCacheKey(countMappedStatement, parameterObject, pagingRowBounds, countBoundSql);
        return (Integer)executor.query(countMappedStatement, parameterObject, pagingRowBounds, resultHandler, cacheKey, countBoundSql).get(0);
    }

    private BoundSql createCountSql(MappedStatement mappedStatement, BoundSql originBoundSql) {
        // select count(1) from (select * from dfkdsjkfjdsk wehre id ！= 666)
        String countSql = "SELECT COUNT(1) FROM (" + originBoundSql.getSql() + ")";
        // 创建新的SQL来进行执行统计！
        return new BoundSql(mappedStatement.getConfiguration(), countSql, originBoundSql.getParameterMappings(), originBoundSql.getParameterObject());
    }

    // 重新创建MappedStatement，然后来创建新的MappedStatement用于查询count的值
    private MappedStatement createCountMappedStatement(MappedStatement mappedStatement) {
        List<ResultMap> resultMaps = new ArrayList();
        ResultMap resultMap = (new Builder(mappedStatement.getConfiguration(), mappedStatement.getId(), Integer.class, new ArrayList())).build();
        resultMaps.add(resultMap);
        return (new org.apache.ibatis.mapping.MappedStatement.Builder(mappedStatement.getConfiguration(), mappedStatement.getId(), mappedStatement.getSqlSource(), mappedStatement.getSqlCommandType())).statementType(mappedStatement.getStatementType()).cache(mappedStatement.getCache()).fetchSize(mappedStatement.getFetchSize()).flushCacheRequired(mappedStatement.isFlushCacheRequired()).resource(mappedStatement.getResource()).keyGenerator(mappedStatement.getKeyGenerator()).parameterMap(mappedStatement.getParameterMap()).timeout(mappedStatement.getTimeout()).resultOrdered(mappedStatement.isResultOrdered()).resultSetType(mappedStatement.getResultSetType()).useCache(mappedStatement.isUseCache()).resultMaps(resultMaps).build();
    }

    private List executePagingSql(Executor executor, MappedStatement mappedStatement, Object parameterObject, PagingRowBounds pagingRowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        BoundSql pagingBoundSql = this.createPagingSql(mappedStatement, boundSql, pagingRowBounds);
        CacheKey cacheKey = executor.createCacheKey(mappedStatement, parameterObject, pagingRowBounds, pagingBoundSql);
        return executor.query(mappedStatement, parameterObject, pagingRowBounds, resultHandler, cacheKey, pagingBoundSql);
    }

    private BoundSql createPagingSql(MappedStatement mappedStatement, BoundSql originBoundSql, PagingRowBounds pagingRowBounds) {
        // Oracle用法
        // String pagingSql = "SELECT * FROM (SELECT ROWNUM AS ROW_NUM, A.* FROM (" + originBoundSql.getSql() + ") A) WHERE ROW_NUM >= " + pagingRowBounds.getStartRowNum() + " AND ROW_NUM <= " + pagingRowBounds.getEndRowNum();
        // MySQL用法
        String pagingSql = originBoundSql.getSql() +  " limit " +pagingRowBounds.getStartRowNum()+" , "+pagingRowBounds.getEndRowNum();
        return new BoundSql(mappedStatement.getConfiguration(), pagingSql, originBoundSql.getParameterMappings(), originBoundSql.getParameterObject());
    }

    // 校验类型是否是以select开头的
    private void validate(MappedStatement mappedStatement, BoundSql boundSql) {
        if (SqlCommandType.SELECT != mappedStatement.getSqlCommandType()) {
            throw new IllegalArgumentException("该sql语句不是select语句: " + boundSql.getSql());
        }
    }
}
